Fork me on GitHub

大白话讲解 Koa2 洋葱模型

作者:gauseen
原文:https://github.com/gauseen/blog

Koa.js

Koa.js 是一个极其精简的 Web 服务框架,主要提供以下功能:

  • HTTP 服务:主要处理 requestresponse
  • 中间件数据处理机制(洋葱模型)

什么是 AOP?

AOP 为 Aspect Oriented Programming 的缩写,中文意思为:面向切面编程,它是函数式编程的一种衍生范式

举个栗子 :chestnut: :

假如我想把一个苹果(源数据)处理成果盘(最终数据)我该怎么做?

① 苹果(源数据) —->
② 洗苹果 —->
③ 切苹果 —->
④ 放入盘子 —->
⑤ 果盘(最终数据)

共有 5 个步骤,如果我想升级一下果盘,打算在切苹果之前先削皮,放入盘子后摆成五角星形状那么我的步骤应该如下:

① 苹果(源数据) —->
② 洗苹果 —->
③ 削皮 —->
④ 切苹果 —->
⑤ 放入盘子 —->
⑥ 摆成五角星形状 —->
⑦ 果盘(最终数据)

上面每个步骤都可以看成相应的方法,步骤 ③ 和 ⑥ 加入与否都不影响我制作出果盘这个结果,可以看出这样是非常灵活的

其实这就是生活中面向切面编程的例子,
换句话说,就是在现有程序中,加入或减去一些功能不影响原有的代码功能。

什么是 Koa.js 洋葱模型?

洋葱模型其实就是中间件处理的流程,中间件生命周期大致有:

  • 前期处理
  • 交给并等待其它中间件处理
  • 后期处理

多个中间件处理,就形成了所谓的洋葱模型,它是 AOP 面向切面编程的一种应用。

结合一下上面的果盘例子可知,在 Koa.js 中,苹果(源数据)就是 请求数据 request,果盘(最终数据)就是 响应数据 response,中间处理的过程就是 Koa2.js 的中间件函数处理的过程

一张经典的洋葱切面图如下:

先回顾一下,Koa2.js 中下面代码打印输出顺序为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const Koa = require('koa')
const app = new Koa()

app.use(async (cxt, next) => {
console.log('middleware_01 start')
await next()
console.log('middleware_01 end')
})

app.use(async (cxt, next) => {
console.log('middleware_02 start')
await next()
console.log('middleware_02 end')
})

app.use(async (cxt, next) => {
console.log('middleware_03 start')
console.log('middleware_03 end')
})

app.listen(3000)
1
2
3
4
5
6
7
8
9
// 浏览器访问:http://localhost:3000
// 输出顺序为:

middleware_01 start
middleware_02 start
middleware_03 start
middleware_03 end
middleware_02 end
middleware_01 end

如何实现洋葱模型(中间件机制)

想一想,怎样才能实现 Koa.js 中间件处理机制呢?

最简单版,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 函数处理的数据
let context = {}

function middleware_01 (cxt) {
console.log('middleware_01 start')
middleware_02(cxt)
console.log('middleware_01 end')
}

function middleware_02 (cxt) {
console.log('middleware_02 start')
middleware_03(cxt)
console.log('middleware_02 end')
}

function middleware_03 (cxt) {
console.log('middleware_03 start')
console.log('middleware_03 end')
}

// 调用中间件 compose 函数
function compose () {
// 默认调用第一个中间件
middleware_01(context)
}

compose()

// 输出结果如下,与上面中间件一致:

middleware_01 start
middleware_02 start
middleware_03 start
middleware_03 end
middleware_02 end
middleware_01 end

上面代码虽然实现了,但是有不足点,如:

  • 要显示指明要调用的函数名称,不够灵活

升级版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const App = function () {
// 中间件公共的处理数据
let context = {}
// 中间件队列
let middlewares = []
return {
// 将中间件放入队列中
use (fn) {
middlewares.push(fn)
},
// 调用中间件
callback () {
// 初始调用 middlewares 队列中的第 1 个中间件
return dispatch(0)
function dispatch (i) {
// 获取要执行的中间件函数
let fn = middlewares[i]
// 执行中间件函数,回调参数是:公共数据、调用下一个中间件函数
// 返回一个 Promise 实例
return Promise.resolve(
fn(context, function next () { dispatch(i + 1) })
)
}
},
}
}

上面代码,在不考虑特殊边界情况下,就完成了 Koa2.js 中简易版中间件的封装,让我们来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 测试代码

let app = App()

app.use(async (cxt, next) => {
console.log('middleware_01 start')
await next()
console.log('middleware_01 end')
})

app.use(async (cxt, next) => {
console.log('middleware_02 start')
await next()
console.log('middleware_02 end')
})

app.use(async (cxt, next) => {
console.log('middleware_03 start')
console.log('middleware_03 end')
})

// Koa2.js 源码中,放在 http.createServer(callback) 回调中调用
// 这里我们直接调用
app.callback()

// 输出如下:

middleware_01 start
middleware_02 start
middleware_03 start
middleware_03 end
middleware_02 end
middleware_01 end

想更深入的了解 Koa2.js 洋葱模型可在这里看源码


欢迎关注无广告文章公众号:学前端

参考

-------------我是有底线哒-------------